from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
from enum import Enum


class GradeType(Enum):
    """Grade type enumeration"""
    NUMERIC = "numeric"
    LETTER = "letter"
    PASS_FAIL = "pass_fail"
    PERCENTAGE = "percentage"


class LetterGrade(Enum):
    """Letter grade enumeration"""
    A_PLUS = "A+"
    A = "A"
    A_MINUS = "A-"
    B_PLUS = "B+"
    B = "B"
    B_MINUS = "B-"
    C_PLUS = "C+"
    C = "C"
    C_MINUS = "C-"
    D_PLUS = "D+"
    D = "D"
    F = "F"


class PassFailGrade(Enum):
    """Pass/Fail grade enumeration"""
    PASS = "PASS"
    FAIL = "FAIL"


@dataclass
class Grade:
    """Grade class"""
    grade_id: str
    student_id: str
    assignment_id: str
    grader_id: str
    course_id: str
    max_points: float
    grade_type: Optional[GradeType] = None
    numeric_grade: Optional[float] = None
    letter_grade: Optional[LetterGrade] = None
    pass_fail_grade: Optional[PassFailGrade] = None
    percentage_grade: Optional[float] = None
    feedback: str = ""
    comments: str = ""
    rubric_details: dict = field(default_factory=dict)
    graded_at: datetime = field(default_factory=datetime.now)
    last_updated: datetime = field(default_factory=datetime.now)
    is_final: bool = False
    
    def __post_init__(self):
        """Post-initialization processing"""
        # If no grade type specified, default to numeric grade
        if self.grade_type is None:
            self.grade_type = GradeType.NUMERIC
        
        # If numeric grade exists but no percentage grade, calculate percentage
        if self.numeric_grade is not None and self.percentage_grade is None:
            self.percentage_grade = (self.numeric_grade / self.max_points) * 100 if self.max_points > 0 else 0
        
        # If percentage grade exists but no letter grade, calculate letter grade
        if self.percentage_grade is not None and self.letter_grade is None:
            self.letter_grade = self._calculate_letter_grade(self.percentage_grade)
    
    def _calculate_letter_grade(self, percentage: float) -> Optional[LetterGrade]:
        """Calculate letter grade based on percentage"""
        if percentage >= 97:
            return LetterGrade.A_PLUS
        elif percentage >= 93:
            return LetterGrade.A
        elif percentage >= 90:
            return LetterGrade.A_MINUS
        elif percentage >= 87:
            return LetterGrade.B_PLUS
        elif percentage >= 83:
            return LetterGrade.B
        elif percentage >= 80:
            return LetterGrade.B_MINUS
        elif percentage >= 77:
            return LetterGrade.C_PLUS
        elif percentage >= 73:
            return LetterGrade.C
        elif percentage >= 70:
            return LetterGrade.C_MINUS
        elif percentage >= 67:
            return LetterGrade.D_PLUS
        elif percentage >= 60:
            return LetterGrade.D
        else:
            return LetterGrade.F
    
    def set_numeric_grade(self, grade: float) -> None:
        """Set numeric grade"""
        self.numeric_grade = grade
        self.percentage_grade = (grade / self.max_points) * 100 if self.max_points > 0 else 0
        self.letter_grade = self._calculate_letter_grade(self.percentage_grade)
        self.last_updated = datetime.now()
    
    def set_letter_grade(self, grade: LetterGrade) -> None:
        """Set letter grade"""
        self.letter_grade = grade
        self.grade_type = GradeType.LETTER
        self.last_updated = datetime.now()
    
    def set_pass_fail_grade(self, grade: PassFailGrade) -> None:
        """Set pass/fail grade"""
        self.pass_fail_grade = grade
        self.grade_type = GradeType.PASS_FAIL
        self.last_updated = datetime.now()
    
    def set_percentage_grade(self, percentage: float) -> None:
        """Set percentage grade"""
        self.percentage_grade = percentage
        self.numeric_grade = (percentage / 100) * self.max_points
        self.letter_grade = self._calculate_letter_grade(percentage)
        self.last_updated = datetime.now()
    
    def add_rubric_detail(self, criterion: str, score: float, max_points: float, feedback: str = "") -> None:
        """Add rubric detail"""
        self.rubric_details[criterion] = {
            'score': score,
            'max_points': max_points,
            'feedback': feedback
        }
        
        # Update total score
        total_score = sum(detail['score'] for detail in self.rubric_details.values())
        self.set_numeric_grade(total_score)
    
    def get_rubric_score(self) -> float:
        """Get rubric total score"""
        return sum(detail['score'] for detail in self.rubric_details.values())
    
    def get_rubric_max_points(self) -> float:
        """Calculate maximum possible points from rubric"""
        return sum(detail['max_points'] for detail in self.rubric_details.values())
    
    def finalize_grade(self) -> None:
        """Mark grade as final"""
        self.is_final = True
        self.last_updated = datetime.now()
    
    def is_passing(self) -> bool:
        """Check if grade is passing"""
        if self.grade_type == GradeType.PASS_FAIL:
            return self.pass_fail_grade == PassFailGrade.PASS
        elif self.percentage_grade is not None:
            return self.percentage_grade >= 60  # Assume 60% as passing threshold
        elif self.letter_grade is not None:
            return self.letter_grade not in [LetterGrade.F]
        return False
    
    def get_grade_display(self) -> str:
        """Get display string for grade"""
        if self.grade_type == GradeType.NUMERIC:
            return f"{self.numeric_grade}/{self.max_points}"
        elif self.grade_type == GradeType.LETTER:
            return self.letter_grade.value if self.letter_grade else "N/A"
        elif self.grade_type == GradeType.PASS_FAIL:
            return self.pass_fail_grade.value if self.pass_fail_grade else "N/A"
        elif self.grade_type == GradeType.PERCENTAGE:
            return f"{self.percentage_grade:.1f}%"
        else:
            return "No Grade"
    
    def to_dict(self) -> dict:
        """Convert grade object to dictionary for serialization"""
        return {
            'grade_id': self.grade_id,
            'student_id': self.student_id,
            'assignment_id': self.assignment_id,
            'grader_id': self.grader_id,
            'course_id': self.course_id,
            'max_points': self.max_points,
            'grade_type': self.grade_type.value if self.grade_type else None,
            'numeric_grade': self.numeric_grade,
            'letter_grade': self.letter_grade.value if self.letter_grade else None,
            'pass_fail_grade': self.pass_fail_grade.value if self.pass_fail_grade else None,
            'percentage_grade': self.percentage_grade,
            'feedback': self.feedback,
            'comments': self.comments,
            'rubric_details': self.rubric_details,
            'graded_at': self.graded_at.isoformat(),
            'last_updated': self.last_updated.isoformat(),
            'is_final': self.is_final
        }
    
    @classmethod
    def from_dict(cls, data: dict) -> 'Grade':
        """Create grade instance from dictionary"""
        grade = cls(
            data['grade_id'],
            data['student_id'],
            data['assignment_id'],
            data['grader_id'],
            data['course_id'],
            data['max_points']
        )
        
        # Set grade type and value
        if data.get('grade_type'):
            grade.grade_type = GradeType(data['grade_type'])
        
        grade.numeric_grade = data.get('numeric_grade')
        grade.percentage_grade = data.get('percentage_grade')
        grade.feedback = data.get('feedback', '')
        grade.comments = data.get('comments', '')
        grade.rubric_details = data.get('rubric_details', {})
        grade.is_final = data.get('is_final', False)
        
        # Handle enum values
        if data.get('letter_grade'):
            grade.letter_grade = LetterGrade(data['letter_grade'])
        
        if data.get('pass_fail_grade'):
            grade.pass_fail_grade = PassFailGrade(data['pass_fail_grade'])
        
        # Parse datetime strings
        grade.graded_at = datetime.fromisoformat(data['graded_at'])
        grade.last_updated = datetime.fromisoformat(data['last_updated'])
        
        return grade